// EnumDirItems.cpp

#include "StdAfx.h"

#include "EnumDirectory.h"

#include <memory>

namespace Common
{
namespace FileSystem
{

void AddDirFileInfo(int phyParent, int logParent,
					const File::CFileInfo &fi, std::vector<std::shared_ptr<CDirItem> > &dirItems)
{
	auto di = std::make_shared<CDirItem>() ;
  *di = fi;
  di->PhyParent = phyParent;
  di->LogParent = logParent;
  di->Name = fi.Name;
  dirItems.push_back(di);
}

CDirItem& CDirItem::operator=(const CDirItem & obj)
{
	if(this == &obj)
		return *this;
	CFileInfo::operator=(obj);
	PhyParent = obj.PhyParent;
	LogParent = obj.LogParent;
	return *this;
}

 CDirItem& CDirItem::operator=(const CFileInfo & obj)
 {
	 CFileInfo::operator=(obj);
	 return *this;
 }

CString CDirItems::GetPrefixesPath(const std::deque<int> &parents, int index, const CString &name) const
{
  CString path;
  int len = name.GetLength();
  int i;
  for (i = index; i >= 0; i = parents[i])
	len += Prefixes[i].GetLength();
  int totalLen = len;
  TCHAR *p = path.GetBuffer(len);
  p[len] = 0;
  len -= name.GetLength();
  memcpy(p + len, (const TCHAR *)name, name.GetLength() * sizeof(TCHAR));
  for (i = index; i >= 0; i = parents[i])
  {
	const CString &s = Prefixes[i];
	len -= s.GetLength();
	memcpy(p + len, (const wchar_t *)s, s.GetLength() * sizeof(TCHAR));
  }
  path.ReleaseBuffer(totalLen);
  return path;
}

CString CDirItems::GetPhyPath(const std::shared_ptr<CDirItem> & item) const
{
	return GetPrefixesPath(PhyParents, item->PhyParent, item->Name);
}

CString CDirItems::GetPhyPath(int index) const
{
	const PtrItems pt = Items[index];
	return GetPrefixesPath(PhyParents, pt->PhyParent, pt->Name);
}

CString CDirItems::GetLogPath(int index) const
{
  const PtrItems pt = Items[index];
  return GetPrefixesPath(LogParents, pt->LogParent, pt->Name);
}

void CDirItems::ReserveDown()
{
	Prefixes.shrink_to_fit();
	PhyParents.shrink_to_fit();
	LogParents.shrink_to_fit();
	Items.shrink_to_fit();
}

size_t CDirItems::AddPrefix(int phyParent, int logParent, const CString &prefix)
{
	PhyParents.push_back(phyParent);
	LogParents.push_back(logParent);
	Prefixes.push_back(prefix);
	return Prefixes.size() - 1;
}

void CDirItems::DeleteLastPrefix()
{
	PhyParents.pop_back();
	LogParents.pop_back();
	Prefixes.pop_back();
}

void CDirItems::EnumerateDirectory(int phyParent, int logParent, const CString &phyPrefix,
	CStringVector &errorPaths, std::vector<DWORD> &errorCodes)
{
  Find::CEnumerator enumerator(phyPrefix + (wchar_t)FileName::kAnyStringWildcard);
  for (;;)
  {
	File::CFileInfo fi;
	bool found;
	if (!enumerator.Next(&fi, found))
	{
		errorCodes.push_back(::GetLastError());
	  errorPaths.push_back(phyPrefix);
	  return;
	}
	if (!found)
	  break;
	AddDirFileInfo(phyParent, logParent, fi, Items);
	if (fi.IsDir())
	{
	  const CString name2 = fi.Name + (wchar_t)FileName::kDirDelimiter;
	  int parent = AddPrefix(phyParent, logParent, name2);
	  EnumerateDirectory(parent, parent, phyPrefix + name2, errorPaths, errorCodes);
	}
  }
}

void CDirItems::EnumerateDirItems2(const CString &phyPrefix, const CString &logPrefix,
	const CStringVector &filePaths, CStringVector &errorPaths, std::vector<DWORD> &errorCodes)
{
  int phyParent = phyPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, phyPrefix);
  int logParent = logPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, logPrefix);

  for (size_t i = 0; i < filePaths.size(); i++)
  {
	const CString &filePath = filePaths[i];
	File::CFileInfo fi;
	const CString phyPath = phyPrefix + filePath;
	if (!fi.Find(phyPath))
	{
		errorCodes.push_back(::GetLastError());
	  errorPaths.push_back(phyPath);
	  continue;
	}
	int delimiter = filePath.ReverseFind((wchar_t)FileName::kDirDelimiter);
	CString phyPrefixCur;
	int phyParentCur = phyParent;
	if (delimiter >= 0)
	{
	  phyPrefixCur = filePath.Left(delimiter + 1);
	  phyParentCur = AddPrefix(phyParent, logParent, phyPrefixCur);
	}
	AddDirFileInfo(phyParentCur, logParent, fi, Items);
	if (fi.IsDir())
	{
	  const CString name2 = fi.Name + (wchar_t)FileName::kDirDelimiter;
	  int parent = AddPrefix(phyParentCur, logParent, name2);
	  EnumerateDirectory(parent, parent, phyPrefix + phyPrefixCur + name2, errorPaths, errorCodes);
	}
  }
  ReserveDown();
}

static HRESULT EnumerateDirItems(const std::shared_ptr<Wildcard::CCensorNode> &curNode,
	int phyParent, int logParent, const CString &phyPrefix,
	const CStringVector &addArchivePrefix,
	CDirItems &dirItems,
	bool enterToSubFolders,
	IEnumDirItemCallback *callback,
	CStringVector &errorPaths,
	std::vector<DWORD> &errorCodes);

static HRESULT EnumerateDirItems_Spec(const std::shared_ptr<Wildcard::CCensorNode> & curNode,
	int phyParent, int logParent, const CString &curFolderName,
	const CString &phyPrefix,
	const CStringVector &addArchivePrefix,
	CDirItems &dirItems,
	bool enterToSubFolders,
	IEnumDirItemCallback *callback,
	CStringVector &errorPaths,
	std::vector<DWORD> &errorCodes)
  
{
	
  const CString name2 = curFolderName + (wchar_t)FileName::kDirDelimiter;
  int parent = dirItems.AddPrefix(phyParent, logParent, name2);
  size_t numItems = dirItems.Items.size();
  HRESULT res = EnumerateDirItems(curNode, parent, parent, phyPrefix + name2,
	addArchivePrefix, dirItems, enterToSubFolders, callback, errorPaths, errorCodes);
  if (numItems == dirItems.Items.size())
	dirItems.DeleteLastPrefix();
  return res;
}


static HRESULT EnumerateDirItems(const std::shared_ptr<Wildcard::CCensorNode> &curNode,
	int phyParent, int logParent, const CString &phyPrefix,
	const CStringVector &addArchivePrefix,  // prefix from curNode
	CDirItems &dirItems,
	bool enterToSubFolders,
	IEnumDirItemCallback *callback,
	CStringVector &errorPaths,
	std::vector<DWORD> &errorCodes)
{
  if (!enterToSubFolders)
	if (curNode->NeedCheckSubDirs())
	  enterToSubFolders = true;
  HRESULT hRes(S_OK);
  if (callback)
  {
	hRes = callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.size(), phyPrefix);
	if(S_OK != hRes)
		return hRes;
  }

  // try direct_names case at first
  if (addArchivePrefix.empty() && !enterToSubFolders)
  {
	// check that all names are direct
	size_t i;
	for (i = 0; i < curNode->IncludeItems.size(); i++)
	{
		const auto &item = curNode->IncludeItems[i];
	  if (item->Recursive || item->PathParts.size() != 1)
		break;
	  const CString &name = item->PathParts.front();
	  if (name.IsEmpty() || DoesNameContainWildCard(name))
		break;
	}
	if (i == curNode->IncludeItems.size())
	{
	  // all names are direct (no wildcards)
	  // so we don't need file_system's dir enumerator
	  typedef std::vector<bool> boolVector;
	  boolVector needEnterVector;
	  for (i = 0; i < curNode->IncludeItems.size(); i++)
	  {
		const auto &item = curNode->IncludeItems[i];
		const CString &name = item->PathParts.front();
		const CString fullPath = phyPrefix + name;
		File::CFileInfo fi;
		if (!fi.Find(fullPath))
		{
		  errorCodes.push_back(::GetLastError());
		  errorPaths.push_back(fullPath);
		  continue;
		}
		bool isDir = fi.IsDir();
		if (isDir && !item->ForDir || !isDir && !item->ForFile)
		{
		  errorCodes.push_back((DWORD)E_FAIL);
		  errorPaths.push_back(fullPath);
		  continue;
		}
		{
		  CStringVector pathParts;
		  pathParts.push_back(fi.Name);
		  if (curNode->CheckPathToRoot(false, pathParts, !isDir))
			continue;
		}
		AddDirFileInfo(phyParent, logParent, fi, dirItems.Items);
		if (!isDir)
		  continue;
		
		CStringVector addArchivePrefixNew;
		std::shared_ptr<Wildcard::CCensorNode> nextNode = 0;
		int index = curNode->FindSubNode(name);
		if (index >= 0)
		{
			for (boolVector::size_type t = needEnterVector.size(); 
							t <= (boolVector::size_type)index; t++)
			  needEnterVector.push_back(true);
		  needEnterVector[index] = false;
		  nextNode = curNode->SubNodes[index];
		}
		else
		{
		  nextNode = curNode;
		  addArchivePrefixNew.push_back(name); // don't change it to fi.Name. It's for shortnames support
		}

		hRes = EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix,
			addArchivePrefixNew, dirItems, true, callback, errorPaths, errorCodes);
		if(S_OK != hRes)
			return hRes;
	  }
	  for (i = 0; i < curNode->SubNodes.size(); i++)
	  {
		if (i < needEnterVector.size())
		  if (!needEnterVector[i])
			continue;
		const auto & nextNode = curNode->SubNodes[i];
		const CString fullPath = phyPrefix + nextNode->Name;
		File::CFileInfo fi;
		if (!fi.Find(fullPath))
		{
		  if (!nextNode->AreThereIncludeItems())
			continue;
		  errorCodes.push_back(::GetLastError());
		  errorPaths.push_back(fullPath);
		  continue;
		}
		if (!fi.IsDir())
		{
		  errorCodes.push_back((DWORD)E_FAIL);
		  errorPaths.push_back(fullPath);
		  continue;
		}

		hRes = EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix,
			CStringVector(), dirItems, false, callback, errorPaths, errorCodes);
		if(S_OK != hRes)
			return hRes;
	  }
	  return S_OK;
	}
  }


  Find::CEnumerator enumerator(phyPrefix + wchar_t(FileName::kAnyStringWildcard));
  for (int ttt = 0; ; ttt++)
  {
	File::CFileInfo fi;
	bool found;
	if (!enumerator.Next(&fi, found))
	{
	  errorCodes.push_back(::GetLastError());
	  errorPaths.push_back(phyPrefix);
	  break;
	}
	if (!found)
	  break;

	if (callback && (ttt & 0xFF) == 0xFF)
	{
		hRes = callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.size(), phyPrefix);
		if(S_OK != hRes)
			return hRes;
	}

	const CString &name = fi.Name;
	bool enterToSubFolders2 = enterToSubFolders;
	CStringVector addArchivePrefixNew = addArchivePrefix;
	addArchivePrefixNew.push_back(name);
	{
	  CStringVector addArchivePrefixNewTemp(addArchivePrefixNew);
	  if (curNode->CheckPathToRoot(false, addArchivePrefixNewTemp, !fi.IsDir()))
		continue;
	}
	if (curNode->CheckPathToRoot(true, addArchivePrefixNew, !fi.IsDir()))
	{
	  AddDirFileInfo(phyParent, logParent, fi, dirItems.Items);
	  if (fi.IsDir())
		enterToSubFolders2 = true;
	}
	if (!fi.IsDir())
	  continue;

	std::shared_ptr<Wildcard::CCensorNode> nextNode;
	if (addArchivePrefix.empty())
	{
	  int index = curNode->FindSubNode(name);
	  if (index >= 0)
		nextNode = curNode->SubNodes[index];
	}
	if (!enterToSubFolders2 && nextNode == 0)
	  continue;

	addArchivePrefixNew = addArchivePrefix;
	if (nextNode == 0)
	{
	  nextNode = curNode;
	  addArchivePrefixNew.push_back(name);
	}

	hRes = EnumerateDirItems_Spec(nextNode, phyParent, logParent, name, phyPrefix,
		addArchivePrefixNew, dirItems, enterToSubFolders2, callback, errorPaths, errorCodes);
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

HRESULT EnumerateItems(
	const Wildcard::CCensor &censor,
	CDirItems &dirItems,
	IEnumDirItemCallback *callback,
	CStringVector &errorPaths,
	std::vector<DWORD> &errorCodes)
{
  HRESULT hRes(S_OK);
  for (size_t i = 0; i < censor.Pairs.size(); i++)
  {
	  const Wildcard::CPair pair = censor.Pairs[i];
	int phyParent = pair.Prefix.IsEmpty() ? -1 : dirItems.AddPrefix(-1, -1, pair.Prefix);
	hRes = EnumerateDirItems(pair.Head, phyParent, -1, pair.Prefix, CStringVector(), dirItems, false,
		callback, errorPaths, errorCodes);
	if(S_OK != hRes)
		return hRes;
  }
  dirItems.ReserveDown();
  return S_OK;
}

}
}